package netcode.server;

import gamePlay.Bet;
import gamePlay.Player;
import gamePlay.Question;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;

import crawler.QuestionDBConnector;

/**
 * This class controls the game play of a networked game on the server
 * 
 * @author Jim Jensen
 * @version April 12, 2012
 */
public class NetworkGame extends Thread {
	public static final boolean VERBOSE = true;
	
	// Status codes for the state of the game.
	public static final int WAITING_TO_START = 0;
	public static final int STARTED = 1;
	public static final int ENDED = 2;
	public static final int MIN_N_PLAYERS = 1;
	public static final int MAX_N_PLAYERS = 6;

	private int gameID;
	private int numRounds;
	private String gameName;
	private int gameState;

	// Map of client keys to ConnectedClients
	private Map<Integer, ConnectedClient> clients;
	private Map<Integer, ConnectedClient> waitingClients;

	// map of client keys to a list of all that player's bets
	private Map<Integer, ArrayList<Bet>> bets;

	private String[] qCategories;
	private long roundStartTime;
	private long timeToGuess;
	private long timeToBet;
	private Question currentQuestion;

	// map of client keys to guess
	private Map<Integer, Integer> guessMap;
	private List<Question> questionList;
	private ArrayList<Integer> oddsList;
	private ArrayList<Integer> sortedGuesses;

	public NetworkGame() {
	}

	public NetworkGame(int gID, String gName, int cKey, ConnectedClient client,
			String[] categories, int rounds) {

		gameID = gID;

		gameName = gName;

		clients = new ConcurrentSkipListMap<Integer, ConnectedClient>();

		clients.put(cKey, client);

		waitingClients = new ConcurrentSkipListMap<Integer, ConnectedClient>();
		
		bets = new ConcurrentSkipListMap<Integer, ArrayList<Bet>>();

		qCategories = categories;

		numRounds = rounds;

		timeToGuess = 0;

		timeToBet = 0;

		sortedGuesses = new ArrayList<Integer>();

		guessMap = new ConcurrentSkipListMap<Integer, Integer>();
		oddsList = new ArrayList<Integer>();

		gameState = 0;

		grabQuestions();

	}

	public void run() {
		playGame();
	}

	/**
	 * Calling this method starts the game
	 */
	private void playGame() {
		gameState = 1;
		for (int i = 0; i < numRounds; i++) {
			roundStartTime = System.currentTimeMillis();
			currentQuestion = questionList.get(i);

			// send question to players
			Iterator<Integer> it = clients.keySet().iterator();
			while (it.hasNext()) {
				Server.sendQuestion(it.next(), currentQuestion.getText());
			}

			// get answers (Server class handles this)
			// players get 30 seconds to guess, plus 200 ms allowed for latency
			timeToGuess = System.currentTimeMillis() + 30200;
			sleepThread(timeToGuess);

			// calculate odds
			oddsCalc();

			// send odds to players
			it = clients.keySet().iterator();
			while (it.hasNext()) {
				Server.sendBettingOdds(it.next(), gameID, oddsList,
						sortedGuesses);
			}

			// get bets from players (Server class handles this)
			// players get 30 seconds to bet, plus 200 ms allowed for latency
			timeToBet = System.currentTimeMillis() + 30200;
			sleepThread(timeToBet);

			// adds waiting clients to the game
			if (!waitingClients.isEmpty()) {
				clients.putAll(waitingClients);
				sendClientInfo();
			}
			
			// calculate and deal winnings and send round info
			winnings();
			
			// clear answers, bets, odds
			clearAll();

			// give the players 30 seconds to review the rounds stats
			// before starting the next round
			sleepThread(System.currentTimeMillis() + 30000);
		}

		sendGameResults();
		gameState = 2;
	}

	/**
	 * Adds a new ConnectedClient to the game, and sends the client ID of the
	 * new player to all the players.
	 * 
	 * @param cKey
	 * @param client
	 */
	public void addClient(int cKey, ConnectedClient client) {
		if (!clients.containsKey(cKey)) {
			System.out.println("Adding client that's not already in game.");
			
			long currentTime = System.currentTimeMillis();
			
			if(gameState == NetworkGame.STARTED && currentTime > roundStartTime) {
				waitingClients.put(cKey, client);
				System.out.println("Client waiting until the next round");
			} else {
				clients.put(cKey, client);
				System.out.println("Client added");
			}
			sendClientInfo();
		}
	}

	/**
	 * Removes a ConnectedClient from the game, then sends the key of the player
	 * that left to the rest of the players.
	 * 
	 * @param cKey
	 */
	public void removeClient(int cKey) {
		// Check to see if the client is in the game. If so, remove.
		if (clients.containsKey(cKey)) {
			clients.remove(cKey);
			bets.remove(cKey);

			Iterator<Integer> clientKeys = clients.keySet().iterator();
			while (clientKeys.hasNext()) {
				Server.sendGameLobbyInfo(clientKeys.next(), gameID);
			}
		}

		// Check to see if all clients have left the game. If so, end the game
		// and remove it from memory.
		if (clients.isEmpty()) {
			// TODO
			Server.removeNetworkGame(gameID);
		}
	}
	
	private void sendClientInfo() {
		System.out
				.println("Sending game lobby info to all connected clients");
		Iterator<Integer> clientKeys = clients.keySet().iterator();
		while (clientKeys.hasNext()) {
			int clientKey = clientKeys.next();
			Server.sendGameLobbyInfo(clientKey, gameID);
		}
		System.out.println("Sent info.");
		
	}

	/**
	 * Currently populates questionList with as many random questions as there
	 * are rounds
	 */
	private void grabQuestions() {
		QuestionDBConnector dbConnection = new QuestionDBConnector();
		try {
			dbConnection.initialize("jdbc:mysql://localhost/groupwork_db",
					"groupwork", "smiles");
			questionList = dbConnection.getRandomQuestions(numRounds);
			dbConnection.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Registers the player's answer
	 * 
	 * @param clientID
	 *            - the player's id
	 * @param answer
	 *            - the player's answer for the current question
	 */
	public void clientSubmitAnswer(int cKey, int clientGuess) {
		// only register guesses if submitted before time expired
		if (System.currentTimeMillis() < timeToGuess) {
			// (Anthony) changed this to map. Otherwise, you won't know who
			// submitted what.
			if (!guessMap.containsKey(cKey)) {
				guessMap.put(cKey, clientGuess);
			} else {
				// TODO Send some sort of error message.
			}
			// This will get the thread started again when all players have
			// submitted an answer.
			if (guessMap.size() == clients.size())
				this.interrupt();
		}
	}

	/**
	 * Gets a list of bets from a client and puts them in the Player object
	 * 
	 * @param cKey
	 *            client's key
	 * @param bet
	 *            client's bets
	 */
	public void clientSubmitBet(int cKey, ArrayList<Bet> bet) {
		// only register bets if submitted before time expired
		if (System.currentTimeMillis() < timeToBet) {
			if (!bets.containsKey(cKey)) {
				bets.put(cKey, bet);
			}
			// This will get the game started again once all players have
			// submitted a bet.
			if (bets.size() == clients.size())
				this.interrupt();

		}
	}

	// method for calculating the odds on each bet is tiered
	// By Team Good Team, modified by Jim J.
	private void oddsCalc() {
//		int total = 0;
		System.out.println("Entered oddsCalc.");
		Iterator<Integer> gIterator = guessMap.keySet().iterator();
		System.out.println("guessMap Iterator created.");

		while (gIterator.hasNext()) {
			System.out.println("entered the while loop");

			int currentGuess = guessMap.get(gIterator.next());
			System.out.println("currentGuess: " + currentGuess);

			if (!sortedGuesses.contains(currentGuess))
				sortedGuesses.add(currentGuess);

//			total += currentGuess;
//			System.out.println("total: " + total);

			System.out.println("Added to sortedGuesses");
		}

		Collections.sort(sortedGuesses);
		System.out.println("Sorted sortedGuesses, heh");

		for (int i = 0; i < sortedGuesses.size(); i++) {
			System.out.println("in the for loop");

			oddsList.add(i + 2);

			// if (sortedGuesses.get(i) != 0) {
			// oddsList.add(total / sortedGuesses.get(i));
			// System.out.println("Added to oddsList");
			// } else {
			// oddsList.add(5);
			// System.out.println("Added to oddsList");
			// }
		}
	}

	/*
	 * Calculates results from bets To be used after all bets are in. By Good
	 * Team, modified by Jim J.
	 */
	public void winnings() {
		if(VERBOSE){
			System.out.println("Calculating Winnings for game: " + gameID);
		}
		int answer = currentQuestion.getAnswer();
		int winIndex = -1;

		// holds players and their round payout in the format:
		// {p1, p1's payout, p2, p2's payout, ... }
		int[] playersAndWinnings = new int[clients.size() * 2];
		int pwCounter = 0;

		// the winning guess will be the largest number less than or equal to
		// the answer
		for (int i = 0; i < sortedGuesses.size(); i++) {
			if (sortedGuesses.get(i) <= answer) {
				winIndex = i;
			}
		}
		
		if(VERBOSE){
			if(winIndex == -1){
				System.out.println("There were no winners. The winIndex is: " + winIndex);
			}else{
				System.out.println("The winIndex is: " + winIndex + "\nThis makes the winning answer: " + sortedGuesses.get(winIndex));
			}
		}

		// cycle through all the players
		Iterator<Integer> keyIterator = clients.keySet().iterator();
		while (keyIterator.hasNext()) {
			int currentKey = keyIterator.next();
			if(VERBOSE){
				System.out.println("checking the bets of client: " + currentKey);
			}

			// running total of chips won/lost by the player identified by
			// currentKey
			int payout = 0;
			ArrayList<Bet> playerBets = bets.get(currentKey);

			if (playerBets != null) {

				// cycle through all of the current player's bets
				for (int i = 0; i < playerBets.size(); i++) {
					Bet currentBet = playerBets.get(i);
					int betChoice = currentBet.getChoice();
					if(VERBOSE){
						System.out.println("The " + i + "bet is for choice " + betChoice);
					}

					if (betChoice == winIndex) {
						payout += (currentBet.getPermaChips() + currentBet
								.getGameChips()) * oddsList.get(winIndex);
						if(VERBOSE){
							System.out.println("The player was successful. payout is now " + payout);
						}
					}

					else {
						payout -= currentBet.getGameChips();
//						if (payout < 2)
//							payout = 2;
						if(VERBOSE){
							System.out.println("The player was not successful. payout is now " + payout);
						}
					}
				}
			}else{
				if(VERBOSE){
					System.out.println("This player made no bets");
				}
			}

			// get the player's old score, add this round's payout and update
			int score = clients.get(currentKey).getPlayer().getGame_chips();
			clients.get(currentKey).getPlayer().setGame_chips(score + payout);

			// store players winnings for later
			playersAndWinnings[pwCounter] = currentKey;
			playersAndWinnings[pwCounter + 1] = clients.get(currentKey).getPlayer().getGame_chips();
			pwCounter += 2;
		}

		for (int i = 0; i < playersAndWinnings.length; i += 2) {
			Server.sendRoundResults(playersAndWinnings[i], gameID,
					currentQuestion.getAnswer(), playersAndWinnings);
		}

	}

	// send final stats to all players
	private void sendGameResults() {
		Iterator<Integer> it = clients.keySet().iterator();
		// gameResults holds data like this:
		// [client1Key, client1GameChips, client2Key, client2GameChips, ... ]
		int[] gameResults = new int[clients.keySet().size() * 2];
		int i = 0;
		int maxChips = 0;
		int winningPlayerKey = 0;

		while (it.hasNext()) {
			int currentClientKey = it.next();
			gameResults[i] = currentClientKey;

			gameResults[i + 1] = clients.get(currentClientKey).getPlayer()
					.getGame_chips();
			// Jim C.
			if (maxChips < gameResults[i + 1]) {
				maxChips = gameResults[i + 1];
				winningPlayerKey = gameResults[i];
			}

			gameResults[i + 1] = clients.get(currentClientKey).getPlayer()
					.getGame_chips();
			i += 2;

		}

		it = clients.keySet().iterator();

		// sends results to all clients one-by-one
		while (it.hasNext()) {
			Server.sendGameResults(it.next(), gameID, gameResults);
		}

		// Jim C.
		submitPlayerForHighScore(winningPlayerKey, maxChips);
	}

	// Jim C.
	private void submitPlayerForHighScore(int winningPlayerKey, int maxChips) {
		if(VERBOSE){
			System.out.println("Submitting " + winningPlayerKey + " for high score with " + maxChips + " chips.");
		}
		try {
			if(VERBOSE){
				System.out.println("Initializing connection to database.");
			}
			QuestionDBConnector connector = new QuestionDBConnector();
			connector.initialize("jdbc:mysql://localhost/groupwork_db",
					"groupwork", "smiles");
			if(VERBOSE){
				System.out.println("Getting High Score List.");
			}
			List<Player> p = connector.getHighScoreList();

			ConnectedClient client = ConnectedClientFactory
					.getConnectedClient(winningPlayerKey);
			if (client == null) {
				if(VERBOSE){
					System.out.println("Cannot find clientKey");
				}
			} else {
				String name = client.getName();
				if(VERBOSE){
					System.out.println("client Name: "  + name);
				}

				if (p.size() < 10) {

					if (p.size() == 0) {
						connector.putNameInHighScoreList(0, name, maxChips);
					} else {
						connector.putNameInHighScoreList(p.get(p.size() - 1)
								.getPlayerID() + 1, name, maxChips);
					}
				} else {

					int playerIndex = 0;
					int lowScore = p.get(playerIndex).getGame_chips();

					for (int i = 1; i < p.size(); i++) {
						if (lowScore > p.get(i).getGame_chips()) {
							lowScore = p.get(i).getGame_chips();
							playerIndex = i;
						}
					}
					connector.removeNameFromHighScoreList(p.get(playerIndex)
							.getPlayerID());
					connector.putNameInHighScoreList(p.get(playerIndex)
							.getPlayerID(), name, maxChips);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// clears all stats for current round
	private void clearAll() {
		guessMap.clear();
		oddsList.clear();
		sortedGuesses.clear();
		bets.clear();
		waitingClients.clear();
	}

	/**
	 * Sleeps the thread until the current part of the game is done
	 * 
	 * @param targetTime
	 *            time to wake up
	 */
	private void sleepThread(long targetTime) {
		long currentTime = System.currentTimeMillis();

		try {
			sleep(targetTime - currentTime);
		} catch (InterruptedException e) {
			// e.printStackTrace();
			if(VERBOSE){
				System.out.println("All answers/bets submitted, resuming game.");
			}
		}
	}

	public int getGameID() {
		return gameID;
	}

	public String getGameName() {
		return gameName;
	}

	public Map<Integer, ConnectedClient> getConnectedClients() {
		return clients;
	}

	public String[] getCategories() {
		return qCategories;
	}

	public int getGameState() {
		return gameState;
	}

	public int getNConnectedClients() {
		return clients.size();
	}

}
